/*
 * date_utilits.h
 *
 *  Created on: 2021年9月22日
 *      Author: guyadong
 */

#ifndef COMMON_SOURCE_CPP_DATE_UTILITS_H_
#define COMMON_SOURCE_CPP_DATE_UTILITS_H_
#include <chrono>
#include <ctime>
#include <string>
#include <iostream>
#include "date/date.h"
#include <iomanip>
#include <regex>
#include <stdint.h>
namespace date{
	/** 系统时间(毫秒) */
	using time_mills = std::chrono::time_point<std::chrono::system_clock, std::chrono::milliseconds>;

	template <class CharT, class Traits, class Alloc, class Duration>
	auto
	format(const std::basic_string<CharT, Traits, Alloc>& fmt, const sys_time<Duration>& tp, const std::string& abbrev = { "UTC" },
			const std::chrono::seconds& offset_sec = std::chrono::seconds(0))
		-> decltype(to_stream(std::declval<std::basic_ostream<CharT, Traits>&>(), fmt.c_str(), std::declval<local_time<Duration>>(), &abbrev, &offset_sec),
			std::basic_string<CharT, Traits, Alloc>{})
	{
		std::basic_ostringstream<CharT, Traits, Alloc> os;
		os.exceptions(std::ios::failbit | std::ios::badbit);
		local_time<Duration> _lt(tp.time_since_epoch());
		to_stream(os, fmt.c_str(), _lt, &abbrev, &offset_sec);
		return os.str();
	}
	inline bool
		parse_by_get_time(const std::string& input, const std::string &format, std::tm & _tm)
	{
		// Create a stream which we will use to parse the string,
		// which we provide to constructor of stream to fill the buffer.
		std::istringstream ss{ input };

		// Create a tm object to store the parsed date and time.
		_tm = { 0,0,0,1,0,70,0,0,0 };
		// Now we read from buffer using get_time manipulator
		// and formatting the input appropriately.
		//ss.imbue(std::locale("en_GB.utf-8"));
		ss >> std::get_time(&_tm, format.c_str());
		// Convert the tm structure to time_t value and return.
		return !ss.fail();

	}

	// Converts IS8601 time string to a date::time_mills value.
	inline bool parse_by_date(const std::string& input, const std::string &format, date::time_mills& mills, int offset = 0)
	{
		std::istringstream in(input);
		std::chrono::minutes offmin(offset);
		std::string abovabbrev;
		in >> date::parse(format, mills, abovabbrev, offmin);
		return !in.fail();
	}

	//************************************
	// 返回当前时区偏移(分钟)
	// @return   int
	//************************************
	inline int timezone_offset()
	{
		static int offset = INT32_MAX;
		if (INT32_MAX == offset)
		{
			time_t _rt = time(NULL);
			tm _gtm = *gmtime(&_rt);
			tm _ltm = *localtime(&_rt);
			time_t _gt = mktime(&_gtm);
			tm _gtm2 = *localtime(&_gt);
			offset = (int)(((_rt - _gt) + (_gtm2.tm_isdst ? 3600 : 0)) / 60);
		}
		return offset;
	}
	//************************************
	// Converts time string to a std::tm value.
	// Supported format:
	// "%Y-%m-%dT%H:%M:%S"
	// "%Y-%m-%d" 
	// "%H:%M:%S" 
	// @param    const std::string & input
	// @param    std::tm & _tm [out]
	// @return   bool true if parsed.otherwise false.
	//************************************
	inline bool parset_datetime(const std::string& input, std::tm& _tm)
	{
		// Let's consider we are getting all the input in
		// this format: '2014-07-25T20:17:22Z' (T denotes
		// start of Time part, Z denotes UTC zone).
		// A better approach would be to pass in the format as well.
		static const std::string dateTimeFormat{ "%Y-%m-%dT%H:%M:%S" };
		static const std::string dateFormat{ "%Y-%m-%d" };
		static const std::string timeFormat{ "%H:%M:%S" };
		static const std::vector<std::string> fmts{ dateTimeFormat, dateFormat, timeFormat };
		_tm = { 0,0,0,1,0,70,0,0,0 };
		static const std::string p_dateTimeFormat("\\d{2,4}-\\d{1,2}-\\d{1,2}T\\d{1,2}:\\d{1,2}:\\d{1,2}");
		static const std::string p_dateFormat("\\d{2,4}-\\d{1,2}-\\d{1,2}");
		static const std::string p_timeFormat("\\d{1,2}:\\d{1,2}:\\d{1,2}");
		if (std::regex_match(input, std::regex(p_dateTimeFormat)))
		{
			if (parse_by_get_time(input, dateTimeFormat, _tm)) {
				return true;
			}
		}
		if (std::regex_match(input, std::regex(p_dateFormat)))
		{
			if (parse_by_get_time(input, dateFormat, _tm)) {
				return true;
			}
		}
		if (std::regex_match(input, std::regex(p_timeFormat)))
		{
			if (parse_by_get_time(input, timeFormat, _tm)) {
				return true;
			}
		}
		return false;
	}
	//************************************
	// Converts time string to a time_t value.
	// "%Y-%m-%dT%H:%M:%S"
	// "%Y-%m-%d" 
	// "%H:%M:%S" 
	// @param    const std::string & input
	// @param    time_t & _t
	// @return   bool true if parsed.otherwise false.
	//************************************
	inline bool parset_datetime(const std::string& input, time_t& _t)
	{
		std::tm _tm;
		if (parset_datetime(input, _tm))
		{
			_t = std::mktime(&_tm);
			return true;
		}
		return false;
	}
	//************************************
	// Converts time string to a date::time_mills value.
	// "%Y-%m-%dT%H:%M:%S"
	// "%Y-%m-%d" 
	// "%H:%M:%S" 
	// @param    const std::string & input
	// @param    time_mills & mills
	// @return   bool true if parsed.otherwise false.
	//************************************
	inline bool parset_datetime(const std::string& input, date::time_mills& mills)
	{
		time_t _t;
		if (parset_datetime(input, _t))
		{
			auto _sys_clock = std::chrono::system_clock::from_time_t(_t);
			mills = std::chrono::time_point_cast<std::chrono::milliseconds>(_sys_clock);
			return true;
		}
		return false;
	}
	// Converts time string to a date::time_mills value.
	//************************************
	// Converts time string to a date::time_mills value.
	// "%Y-%m-%dT%H:%M:%S"
	// "%Y-%m-%d" 
	// "%H:%M:%S" 
	// @param    const std::string & input
	// @return   date::time_mills
	//************************************
	inline date::time_mills parset_datetime(const std::string& input)
	{
		date::time_mills mills;
		parset_datetime(input, mills);
		return mills;
	}
	//************************************
	// Converts IS8601 time string to a date::time_mills value.
	// Support fomat:
	//    %FT%TZ
	//    %FT%%Z
	//    %FT%T%Ez
	// @param    const std::string & input
	// @param    time_mills & mills
	// @return   bool true if parsed.otherwise false.
	//************************************
	inline bool parse_iso8601(const std::string& input, date::time_mills& mills)
	{
		const static std::string p1{ "%FT%TZ" };
		const static std::string p2{ "%FT%T%Ez" };
		const static std::string p3{ "%FT%T%z" };
		const static std::string p4{ "%FT%T%Z" };
		const static std::string p5{ "%FT%T" };
		static const std::vector<std::string> fmts{ p1, p2, p3,p4, p5 };
		for (auto fmt : fmts)
		{
			/** 没有时区的格式使用当前时区 */
			int offset = p5 == fmt ? timezone_offset() : 0;
			if (parse_by_date(input, fmt, mills, offset))
			{
				return true;
			}
		}
		return false;
	}
	//************************************
	// Converts IS8601 time string to a std::time_t value.
	// Support fomat:
	//    %FT%TZ
	//    %FT%%Z
	//    %FT%T%Ez
	// @param    const std::string & input
	// @param    std::time_t & _time
	// @return   bool true if parsed.otherwise false.
	//************************************
	inline bool parse_iso8601(const std::string& input, std::time_t& _time)
	{
		date::time_mills mills;
		if (parse_iso8601(input, mills))
		{
			_time = (std::time_t)mills.time_since_epoch().count() / 1000;
			return true;
		}
		return false;
	}
	//************************************
	// Converts IS8601 time string to a std::tm value.
	// Support fomat:
	//    %FT%TZ
	//    %FT%%Z
	//    %FT%T%Ez
	// @param    const std::string & input
	// @param    time_mills & mills
	// @return   bool true if parsed.otherwise false.
	//************************************
	inline bool parse_iso8601(const std::string& input, std::tm& _tm)
	{
		date::time_mills mills;
		if (parse_iso8601(input, mills))
		{
			std::time_t _time = (std::time_t)mills.time_since_epoch().count() / 1000;
			std::tm *__tm = std::localtime(&_time);
			_tm = *__tm;
			return true;
		}
		return false;
	}
	//************************************
	// Converts IS8601 time string to a date::time_mills value.
	// Support fomat:
	//    %FT%TZ
	//    %FT%%Z
	//    %FT%T%Ez
	// @param    const std::string & input
	// @return   date::time_mills 
	//************************************
	inline date::time_mills parse_iso8601(const std::string& input)
	{
		date::time_mills mills;
		parse_iso8601(input, mills);
		return mills;
	}
	//************************************
	// Converts time string to a date::time_mills value.
	// Support fomat:
	//    %Y-%m-%dT%H:%M:%S
	//    %Y-%m-%d
	//    %H:%M:%S
	//    %FT%TZ
	//    %FT%%Z
	//    %FT%T%Ez
	// @param    const std::string & input
	// @param    time_mills & mills
	// @return   bool true if parsed.otherwise false.
	//************************************
	inline bool parse_uni_datetime(const std::string& input, date::time_mills& mills)
	{
		if (parset_datetime(input, mills))
		{
			return true;
		}
		if (parse_iso8601(input, mills))
		{
			return true;
		}
		return false;
	}
	//************************************
	// Converts time string to a std::time_t value.
	// Support fomat:
	//    %Y-%m-%dT%H:%M:%S
	//    %Y-%m-%d
	//    %H:%M:%S
	//    %FT%TZ
	//    %FT%%Z
	//    %FT%T%Ez
	// @param    const std::string & input
	// @param    std::time_t & _time
	// @return   bool true if parsed.otherwise false.
	//************************************
	inline bool parse_uni_datetime(const std::string& input, std::time_t& _time)
	{
		date::time_mills mills;
		if (parse_uni_datetime(input, mills))
		{
			_time = (std::time_t)(mills.time_since_epoch().count() / 1000);
			return true;
		}
		return false;
	}
	//************************************
	// Converts time string to a std::tm value.
	// Support fomat:
	//    %Y-%m-%dT%H:%M:%S
	//    %Y-%m-%d
	//    %H:%M:%S
	//    %FT%TZ
	//    %FT%%Z
	//    %FT%T%Ez
	// @param    const std::string & input
	// @param    std::tm & _tm
	// @return   bool true if parsed.otherwise false.
	//************************************
	inline bool parse_uni_datetime(const std::string& input, std::tm& _tm)
	{
		if (parset_datetime(input, _tm)) 
		{
			return true;
		}
		return parse_iso8601(input, _tm);
	}
	//************************************
	// Converts IS8601 time string to a date::time_mills value.
	// Support fomat:
	//    %Y-%m-%dT%H:%M:%S
	//    %Y-%m-%d
	//    %H:%M:%S
	//    %FT%TZ
	//    %FT%%Z
	//    %FT%T%Ez
	// @param    const std::string & input
	// @return   date::time_mills
	//************************************
	inline date::time_mills parse_uni_datetime(const std::string& input)
	{
		date::time_mills mills;
		parse_uni_datetime(input, mills);
		return mills;
	}

	//************************************
	// 系统时间(毫秒)转为本地时间(毫秒)
	// @param    const time_mills & _sys_time
	// @return   date::time_mills
	//************************************
	inline time_mills to_local_time(const date::time_mills&_sys_time)
	{
		return time_mills(std::chrono::milliseconds(_sys_time.time_since_epoch().count() + timezone_offset() * 60 * 1000));
	}
	//************************************
	// 本地时间(毫秒)转为系统时间(毫秒)
	// @param    const time_mills & _loc_time
	// @return   date::time_mills
	//************************************
	inline time_mills to_system_time(const date::time_mills&_loc_time)
	{
		return time_mills(std::chrono::milliseconds(_loc_time.time_since_epoch().count() - timezone_offset() * 60 * 1000));
	}
	//************************************
	// 根据fmt指定的格式将系统时间格式化为字符串
	// @param    const std::string & fmt
	// @param    const time_mills & tp
	// @param    const std::string & abbrev 时区缩写
	// @param    const std::chrono::seconds & offset_sec 时区偏移(秒)
	// @return   std::string
	//************************************
	inline std::string format(const std::string& fmt, const time_mills& tp, const std::string& abbrev = { "UTC" }, const std::chrono::seconds& offset_sec = std::chrono::seconds(0))
	{
		return date::format<>(fmt, to_local_time(tp), abbrev, offset_sec);
	}
	//************************************
	// 系统时间格式化为ISO8601格式字符串
	// @param    const time_mills & tp
	// @return   std::string
	//************************************
	inline std::string format_iso8601(const time_mills& tp)
	{
		return format("%FT%T%z", tp, "", std::chrono::seconds(timezone_offset() * 60));
	}
	//************************************
	// 系统时间格式化为ISO8601格式字符串
	// @param    time_t _time
	// @return   std::string
	//************************************
	inline std::string format_iso8601(time_t _time)
	{
		date::time_mills mills(std::chrono::seconds((int64_t)_time));
		return format_iso8601(mills);
	}
	//************************************
	// 系统时间(毫秒)格式化为ISO8601格式字符串
	// @param    int64_t _time
	// @return   std::string
	//************************************
	inline std::string format_iso8601_mills(int64_t _time)
	{
		date::time_mills mills(std::chrono::milliseconds((int64_t)_time));
		return format_iso8601(mills);
	}
	//************************************
	// 系统时间格式化为ISO8601格式字符串
	// @param    const std::tm & _tm
	// @return   std::string
	//************************************
	inline std::string format_iso8601(const std::tm & _tm)
	{
		std::time_t _time = std::mktime((std::tm*)&_tm);
		return format_iso8601(_time);
	}

} /** namespace date */
namespace std {	

	//************************************
	// 重载操作符,将系统时间输出到ostream
	// @param    ostream & os
	// @param    const date::time_mills & tp
	// @return   std::ostream&
	//************************************
	inline std::ostream& operator<<(std::ostream& os, const date::time_mills& tp)
	{
		return date::operator<<(os, tp);
	}
}/** namespace std */
#endif /* COMMON_SOURCE_CPP_DATE_UTILITS_H_ */
